package org.monkeypuzzler.css;

import java.awt.Point;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;
import org.monkeypuzzler.css.Style;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

/**
 * SeleniumElement is simple model for an HTMLElement in the current Selenium
 * test page. It exists to provide an easier interface for for developers to
 * test against. It also has the benefit of not being live, allowing it to be
 * serialized and manipulated in variety of ways.
 * 
 * @author csmith@monkeypuzzler.org
 * 
 */
public class Element implements Serializable
{
    private static final long serialVersionUID = -5801718085067578626L;

    public static Element getElement(Selenium selenium, String locator)
            throws JSONException, SeleniumException
    {
        Element element = new Element(locator);
        element.update(selenium);
        return element;
    }

    final public String locator;

    private String id;
    private Point position;
    private Integer width;
    private Integer height;

    private Map<String, Object> attributes;
    private Style style;

    public Element(String locator)
    {
        this.locator = locator;
        this.attributes = new HashMap<String, Object>();
        this.style = new Style();
    }

    /**
     * Convenience method for populating the Element from a Selenium object The
     * envisioned usage is that an element can be updated when the
     * specifications change, with minimal effort.
     * 
     * @param selenium
     * @throws JSONException
     * @throws SeleniumException
     */
    public void update(Selenium selenium) throws JSONException,
            SeleniumException
    {
        String raw = selenium.getEval("this.returnJsonFromLocator("
                + this.locator + ");");
        this.update(raw);
    }

    /**
     * Convenience method for populating the Element from a JSON string
     * 
     * @param raw
     * @throws JSONException
     */
    public void update(String raw) throws JSONException
    {
        JSONObject json = new JSONObject(raw);
        update(json);
    }

    /**
     * Populates the element from a JSONObject
     * 
     * @param json
     * @throws JSONException
     */
    public void update(JSONObject json) throws JSONException
    {
        this.width = json.getInt("width");
        this.height = json.getInt("height");
        this.id = json.getString("id");

        JSONObject point = json.getJSONObject("position");
        this.position = new Point(point.getInt("x"), point.getInt("y"));

        this.attributes.clear();

        JSONObject attributes = json.getJSONObject("attributes");
        String[] names = JSONObject.getNames(attributes);
        if (names != null)
            for (String name : names)
                this.attributes.put(name, attributes.getString(name));

        this.style.update(json.getJSONObject("style"));
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((attributes == null) ? 0 : attributes.hashCode());
        result = prime * result + ((height == null) ? 0 : height.hashCode());
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((locator == null) ? 0 : locator.hashCode());
        result = prime * result
                + ((position == null) ? 0 : position.hashCode());
        result = prime * result + ((style == null) ? 0 : style.hashCode());
        result = prime * result + ((width == null) ? 0 : width.hashCode());
        return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Element other = (Element) obj;
        if (attributes == null)
        {
            if (other.attributes != null)
                return false;
        }
        else if (!attributes.equals(other.attributes))
            return false;
        if (height == null)
        {
            if (other.height != null)
                return false;
        }
        else if (!height.equals(other.height))
            return false;
        if (id == null)
        {
            if (other.id != null)
                return false;
        }
        else if (!id.equals(other.id))
            return false;
        if (locator == null)
        {
            if (other.locator != null)
                return false;
        }
        else if (!locator.equals(other.locator))
            return false;
        if (position == null)
        {
            if (other.position != null)
                return false;
        }
        else if (!position.equals(other.position))
            return false;
        if (style == null)
        {
            if (other.style != null)
                return false;
        }
        else if (!style.equals(other.style))
            return false;
        if (width == null)
        {
            if (other.width != null)
                return false;
        }
        else if (!width.equals(other.width))
            return false;
        return true;
    }

    /**
     * @return the id
     */
    public String getId()
    {
        return id;
    }

    /**
     * @param id
     *            the id to set
     */
    public void setId(String id)
    {
        this.id = id;
    }

    /**
     * @return the position
     */
    public Point getPosition()
    {
        return (Point)position.clone();
    }

    /**
     * @param position
     *            the position to set
     */
    public void setPosition(Point position)
    {
        this.position = position;
    }

    /**
     * @return the width
     */
    public Integer getWidth()
    {
        return width;
    }

    /**
     * @param width
     *            the width to set
     */
    public void setWidth(Integer width)
    {
        this.width = width;
    }

    /**
     * @return the height
     */
    public Integer getHeight()
    {
        return height;
    }

    /**
     * @param height
     *            the height to set
     */
    public void setHeight(Integer height)
    {
        this.height = height;
    }

    /**
     * @return the attributes
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> getAttributes()
    {
        return (Map<String, Object>) ((HashMap<String, Object>) attributes).clone();
    }

    /**
     * @param attributes
     *            the attributes to set
     */
    public void setAttributes(Map<String, Object> attributes)
    {
        this.attributes = attributes;
    }

    /**
     * @return the style
     */
    public Style getStyle()
    {
        return style.clone();
    }

    /**
     * @param style
     *            the style to set
     */
    public void setStyle(Style style)
    {
        this.style = style;
    }
}
